Add tests for the edit page for the toy course, and make sure that exporting to github sets up the git repo properly
This commit is contained in:
@@ -1,13 +1,19 @@
|
||||
import json
|
||||
from django.test.client import Client
|
||||
from django.test import TestCase
|
||||
from django.test.client import Client
|
||||
from mock import patch, Mock
|
||||
from override_settings import override_settings
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from path import path
|
||||
|
||||
from student.models import Registration
|
||||
from django.contrib.auth.models import User
|
||||
from xmodule.modulestore.django import modulestore
|
||||
import xmodule.modulestore.django
|
||||
from xmodule.modulestore import Location
|
||||
from contentstore import import_from_xml
|
||||
import copy
|
||||
|
||||
|
||||
def parse_json(response):
|
||||
@@ -25,36 +31,21 @@ def registration(email):
|
||||
return Registration.objects.get(user__email=email)
|
||||
|
||||
|
||||
class AuthTestCase(TestCase):
|
||||
"""Check that various permissions-related things work"""
|
||||
|
||||
def setUp(self):
|
||||
self.email = 'a@b.com'
|
||||
self.pw = 'xyz'
|
||||
self.username = 'testuser'
|
||||
self.client = Client()
|
||||
|
||||
def check_page_get(self, url, expected):
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(resp.status_code, expected)
|
||||
class ContentStoreTestCase(TestCase):
|
||||
def _login(self, email, pw):
|
||||
'''Login. View should always return 200. The success/fail is in the
|
||||
returned json'''
|
||||
resp = self.client.post(reverse('login_post'),
|
||||
{'email': email, 'password': pw})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
return resp
|
||||
|
||||
def test_public_pages_load(self):
|
||||
"""Make sure pages that don't require login load without error."""
|
||||
pages = (
|
||||
reverse('login'),
|
||||
reverse('signup'),
|
||||
)
|
||||
for page in pages:
|
||||
print "Checking '{0}'".format(page)
|
||||
self.check_page_get(page, 200)
|
||||
|
||||
def test_create_account_errors(self):
|
||||
# No post data -- should fail
|
||||
resp = self.client.post('/create_account', {})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
def login(self, email, pw):
|
||||
'''Login, check that it worked.'''
|
||||
resp = self._login(email, pw)
|
||||
data = parse_json(resp)
|
||||
self.assertEqual(data['success'], False)
|
||||
self.assertTrue(data['success'])
|
||||
return resp
|
||||
|
||||
def _create_account(self, username, email, pw):
|
||||
'''Try to create an account. No error checking'''
|
||||
@@ -78,7 +69,7 @@ class AuthTestCase(TestCase):
|
||||
self.assertEqual(data['success'], True)
|
||||
|
||||
# Check both that the user is created, and inactive
|
||||
self.assertFalse(user(self.email).is_active)
|
||||
self.assertFalse(user(email).is_active)
|
||||
|
||||
return resp
|
||||
|
||||
@@ -95,27 +86,44 @@ class AuthTestCase(TestCase):
|
||||
resp = self._activate_user(email)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
# Now make sure that the user is now actually activated
|
||||
self.assertTrue(user(self.email).is_active)
|
||||
self.assertTrue(user(email).is_active)
|
||||
|
||||
|
||||
class AuthTestCase(ContentStoreTestCase):
|
||||
"""Check that various permissions-related things work"""
|
||||
|
||||
def setUp(self):
|
||||
self.email = 'a@b.com'
|
||||
self.pw = 'xyz'
|
||||
self.username = 'testuser'
|
||||
self.client = Client()
|
||||
|
||||
def check_page_get(self, url, expected):
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(resp.status_code, expected)
|
||||
return resp
|
||||
|
||||
def test_public_pages_load(self):
|
||||
"""Make sure pages that don't require login load without error."""
|
||||
pages = (
|
||||
reverse('login'),
|
||||
reverse('signup'),
|
||||
)
|
||||
for page in pages:
|
||||
print "Checking '{0}'".format(page)
|
||||
self.check_page_get(page, 200)
|
||||
|
||||
def test_create_account_errors(self):
|
||||
# No post data -- should fail
|
||||
resp = self.client.post('/create_account', {})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = parse_json(resp)
|
||||
self.assertEqual(data['success'], False)
|
||||
|
||||
def test_create_account(self):
|
||||
self.create_account(self.username, self.email, self.pw)
|
||||
self.activate_user(self.email)
|
||||
|
||||
def _login(self, email, pw):
|
||||
'''Login. View should always return 200. The success/fail is in the
|
||||
returned json'''
|
||||
resp = self.client.post(reverse('login_post'),
|
||||
{'email': email, 'password': pw})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
return resp
|
||||
|
||||
def login(self, email, pw):
|
||||
'''Login, check that it worked.'''
|
||||
resp = self._login(self.email, self.pw)
|
||||
data = parse_json(resp)
|
||||
self.assertTrue(data['success'])
|
||||
return resp
|
||||
|
||||
def test_login(self):
|
||||
self.create_account(self.username, self.email, self.pw)
|
||||
|
||||
@@ -170,3 +178,30 @@ class AuthTestCase(TestCase):
|
||||
self.assertEqual(resp.status_code, 302)
|
||||
|
||||
# Logged in should work.
|
||||
|
||||
TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
|
||||
TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data')
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MODULESTORE)
|
||||
class EditTestCase(ContentStoreTestCase):
|
||||
"""Check that editing functionality works on example courses"""
|
||||
|
||||
def setUp(self):
|
||||
email = 'edit@test.com'
|
||||
password = 'foo'
|
||||
self.create_account('edittest', email, password)
|
||||
self.activate_user(email)
|
||||
self.login(email, password)
|
||||
xmodule.modulestore.django._MODULESTORES = {}
|
||||
|
||||
def check_edit_item(self, test_course_name):
|
||||
import_from_xml('common/test/data/', test_course_name)
|
||||
|
||||
for descriptor in modulestore().get_items(Location(None, None, None, None, None)):
|
||||
print "Checking ", descriptor.location.url()
|
||||
print descriptor.__class__, descriptor.location
|
||||
resp = self.client.get(reverse('edit_item'), {'id': descriptor.location.url()})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
def test_edit_item_toy(self):
|
||||
self.check_edit_item('toy')
|
||||
|
||||
@@ -92,7 +92,6 @@ def edit_item(request):
|
||||
"""
|
||||
# TODO (vshnayder): change name from id to location in coffee+html as well.
|
||||
item_location = request.GET['id']
|
||||
print item_location, request.GET
|
||||
if not has_access(request.user, item_location):
|
||||
raise Http404 # TODO (vshnayder): better error
|
||||
|
||||
|
||||
@@ -13,16 +13,17 @@ from .exceptions import GithubSyncError
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def import_from_github(repo_settings):
|
||||
def setup_repo(repo_settings):
|
||||
"""
|
||||
Imports data into the modulestore based on the XML stored on github
|
||||
Reset the local github repo specified by repo_settings
|
||||
|
||||
repo_settings is a dictionary with the following keys:
|
||||
path: file system path to the local git repo
|
||||
branch: name of the branch to track on github
|
||||
origin: git url for the repository to track
|
||||
"""
|
||||
repo_path = repo_settings['path']
|
||||
data_dir, course_dir = os.path.split(repo_path)
|
||||
course_dir = repo_settings['path']
|
||||
repo_path = settings.GITHUB_REPO_ROOT / course_dir
|
||||
|
||||
if not os.path.isdir(repo_path):
|
||||
Repo.clone_from(repo_settings['origin'], repo_path)
|
||||
@@ -33,8 +34,29 @@ def import_from_github(repo_settings):
|
||||
|
||||
# Do a hard reset to the remote branch so that we have a clean import
|
||||
git_repo.git.checkout(repo_settings['branch'])
|
||||
|
||||
return git_repo
|
||||
|
||||
|
||||
def load_repo_settings(course_dir):
|
||||
"""
|
||||
Returns the repo_settings for the course stored in course_dir
|
||||
"""
|
||||
for repo_settings in settings.REPOS.values():
|
||||
if repo_settings['path'] == course_dir:
|
||||
return repo_settings
|
||||
raise InvalidRepo(course_dir)
|
||||
|
||||
|
||||
def import_from_github(repo_settings):
|
||||
"""
|
||||
Imports data into the modulestore based on the XML stored on github
|
||||
"""
|
||||
course_dir = repo_settings['path']
|
||||
git_repo = setup_repo(repo_settings)
|
||||
git_repo.head.reset('origin/%s' % repo_settings['branch'], index=True, working_tree=True)
|
||||
module_store = import_from_xml(data_dir, course_dirs=[course_dir])
|
||||
|
||||
module_store = import_from_xml(settings.GITHUB_REPO_ROOT, course_dirs=[course_dir])
|
||||
return git_repo.head.commit.hexsha, module_store.courses[course_dir]
|
||||
|
||||
|
||||
@@ -44,14 +66,16 @@ def export_to_github(course, commit_message, author_str=None):
|
||||
and push to github (if MITX_FEATURES['GITHUB_PUSH'] is True).
|
||||
If author_str is specified, uses it in the commit.
|
||||
'''
|
||||
repo_path = settings.DATA_DIR / course.metadata.get('data_dir', course.location.course)
|
||||
fs = OSFS(repo_path)
|
||||
course_dir = course.metadata.get('data_dir', course.location.course)
|
||||
repo_settings = load_repo_settings(course_dir)
|
||||
git_repo = setup_repo(repo_settings)
|
||||
|
||||
fs = OSFS(git_repo.working_dir)
|
||||
xml = course.export_to_xml(fs)
|
||||
|
||||
with fs.open('course.xml', 'w') as course_xml:
|
||||
course_xml.write(xml)
|
||||
|
||||
git_repo = Repo(repo_path)
|
||||
if git_repo.is_dirty():
|
||||
git_repo.git.add(A=True)
|
||||
if author_str is not None:
|
||||
|
||||
@@ -10,36 +10,37 @@ from xmodule.modulestore import Location
|
||||
from override_settings import override_settings
|
||||
from github_sync.exceptions import GithubSyncError
|
||||
|
||||
REPO_DIR = settings.GITHUB_REPO_ROOT / 'local_repo'
|
||||
WORKING_DIR = path(settings.TEST_ROOT)
|
||||
REMOTE_DIR = WORKING_DIR / 'remote_repo'
|
||||
|
||||
@override_settings(DATA_DIR=path('test_root'))
|
||||
|
||||
@override_settings(REPOS={
|
||||
'local': {
|
||||
'path': 'local_repo',
|
||||
'origin': REMOTE_DIR,
|
||||
'branch': 'master',
|
||||
}
|
||||
})
|
||||
class GithubSyncTestCase(TestCase):
|
||||
|
||||
def cleanup(self):
|
||||
shutil.rmtree(self.repo_dir, ignore_errors=True)
|
||||
shutil.rmtree(self.remote_dir, ignore_errors=True)
|
||||
shutil.rmtree(REPO_DIR, ignore_errors=True)
|
||||
shutil.rmtree(REMOTE_DIR, ignore_errors=True)
|
||||
modulestore().collection.drop()
|
||||
|
||||
def setUp(self):
|
||||
self.working_dir = path(settings.TEST_ROOT)
|
||||
self.repo_dir = self.working_dir / 'local_repo'
|
||||
self.remote_dir = self.working_dir / 'remote_repo'
|
||||
|
||||
# make sure there's no stale data lying around
|
||||
self.cleanup()
|
||||
|
||||
shutil.copytree('common/test/data/toy', self.remote_dir)
|
||||
shutil.copytree('common/test/data/toy', REMOTE_DIR)
|
||||
|
||||
remote = Repo.init(self.remote_dir)
|
||||
remote = Repo.init(REMOTE_DIR)
|
||||
remote.git.add(A=True)
|
||||
remote.git.commit(m='Initial commit')
|
||||
remote.git.config("receive.denyCurrentBranch", "ignore")
|
||||
|
||||
modulestore().collection.drop()
|
||||
|
||||
self.import_revision, self.import_course = import_from_github({
|
||||
'path': self.repo_dir,
|
||||
'origin': self.remote_dir,
|
||||
'branch': 'master',
|
||||
})
|
||||
self.import_revision, self.import_course = import_from_github(settings.REPOS['local'])
|
||||
|
||||
def tearDown(self):
|
||||
self.cleanup()
|
||||
@@ -48,7 +49,7 @@ class GithubSyncTestCase(TestCase):
|
||||
"""
|
||||
Test that importing from github will create a repo if the repo doesn't already exist
|
||||
"""
|
||||
self.assertEquals(1, len(Repo(self.repo_dir).head.reference.log()))
|
||||
self.assertEquals(1, len(Repo(REPO_DIR).head.reference.log()))
|
||||
|
||||
def test_import_contents(self):
|
||||
"""
|
||||
@@ -66,7 +67,7 @@ class GithubSyncTestCase(TestCase):
|
||||
Test that with the GITHUB_PUSH feature disabled, no content is pushed to the remote
|
||||
"""
|
||||
export_to_github(self.import_course, 'Test no-push')
|
||||
self.assertEquals(1, Repo(self.remote_dir).head.commit.count())
|
||||
self.assertEquals(1, Repo(REMOTE_DIR).head.commit.count())
|
||||
|
||||
@override_settings(MITX_FEATURES={'GITHUB_PUSH': True})
|
||||
def test_export_push(self):
|
||||
@@ -75,7 +76,7 @@ class GithubSyncTestCase(TestCase):
|
||||
"""
|
||||
self.import_course.metadata['display_name'] = 'Changed display name'
|
||||
export_to_github(self.import_course, 'Test push')
|
||||
self.assertEquals(2, Repo(self.remote_dir).head.commit.count())
|
||||
self.assertEquals(2, Repo(REMOTE_DIR).head.commit.count())
|
||||
|
||||
@override_settings(MITX_FEATURES={'GITHUB_PUSH': True})
|
||||
def test_export_conflict(self):
|
||||
@@ -84,7 +85,7 @@ class GithubSyncTestCase(TestCase):
|
||||
"""
|
||||
self.import_course.metadata['display_name'] = 'Changed display name'
|
||||
|
||||
remote = Repo(self.remote_dir)
|
||||
remote = Repo(REMOTE_DIR)
|
||||
remote.git.commit(allow_empty=True, m="Testing conflict commit")
|
||||
|
||||
self.assertRaises(GithubSyncError, export_to_github, self.import_course, 'Test push')
|
||||
|
||||
@@ -44,10 +44,8 @@ PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /mitx/cms
|
||||
REPO_ROOT = PROJECT_ROOT.dirname()
|
||||
COMMON_ROOT = REPO_ROOT / "common"
|
||||
ENV_ROOT = REPO_ROOT.dirname() # virtualenv dir /mitx is in
|
||||
COURSES_ROOT = ENV_ROOT / "data"
|
||||
|
||||
# FIXME: To support multiple courses, we should walk the courses dir at startup
|
||||
DATA_DIR = COURSES_ROOT
|
||||
GITHUB_REPO_ROOT = ENV_ROOT / "data"
|
||||
|
||||
sys.path.append(REPO_ROOT)
|
||||
sys.path.append(PROJECT_ROOT / 'djangoapps')
|
||||
|
||||
@@ -18,6 +18,7 @@ MODULESTORE = {
|
||||
'host': 'localhost',
|
||||
'db': 'xmodule',
|
||||
'collection': 'modulestore',
|
||||
'fs_root': GITHUB_REPO_ROOT,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,35 +32,35 @@ DATABASES = {
|
||||
|
||||
REPOS = {
|
||||
'edx4edx': {
|
||||
'path': DATA_DIR / "edx4edx",
|
||||
'path': "edx4edx",
|
||||
'org': 'edx',
|
||||
'course': 'edx4edx',
|
||||
'branch': 'for_cms',
|
||||
'origin': 'git@github.com:MITx/edx4edx.git',
|
||||
},
|
||||
'6002x-fall-2012': {
|
||||
'path': DATA_DIR / '6002x-fall-2012',
|
||||
'path': '6002x-fall-2012',
|
||||
'org': 'mit.edu',
|
||||
'course': '6.002x',
|
||||
'branch': 'for_cms',
|
||||
'origin': 'git@github.com:MITx/6002x-fall-2012.git',
|
||||
},
|
||||
'6.00x': {
|
||||
'path': DATA_DIR / '6.00x',
|
||||
'path': '6.00x',
|
||||
'org': 'mit.edu',
|
||||
'course': '6.00x',
|
||||
'branch': 'for_cms',
|
||||
'origin': 'git@github.com:MITx/6.00x.git',
|
||||
},
|
||||
'7.00x': {
|
||||
'path': DATA_DIR / '7.00x',
|
||||
'path': '7.00x',
|
||||
'org': 'mit.edu',
|
||||
'course': '7.00x',
|
||||
'branch': 'for_cms',
|
||||
'origin': 'git@github.com:MITx/7.00x.git',
|
||||
},
|
||||
'3.091x': {
|
||||
'path': DATA_DIR / '3.091x',
|
||||
'path': '3.091x',
|
||||
'org': 'mit.edu',
|
||||
'course': '3.091x',
|
||||
'branch': 'for_cms',
|
||||
|
||||
@@ -24,6 +24,7 @@ TEST_ROOT = path('test_root')
|
||||
# Want static files in the same dir for running on jenkins.
|
||||
STATIC_ROOT = TEST_ROOT / "staticfiles"
|
||||
|
||||
GITHUB_REPO_ROOT = TEST_ROOT / "data"
|
||||
|
||||
MODULESTORE = {
|
||||
'default': {
|
||||
@@ -33,6 +34,7 @@ MODULESTORE = {
|
||||
'host': 'localhost',
|
||||
'db': 'test_xmodule',
|
||||
'collection': 'modulestore',
|
||||
'fs_root': GITHUB_REPO_ROOT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class Location(_LocationBase):
|
||||
"""
|
||||
return re.sub('_+', '_', INVALID_CHARS.sub('_', value))
|
||||
|
||||
def __new__(_cls, loc_or_tag, org=None, course=None, category=None, name=None, revision=None):
|
||||
def __new__(_cls, loc_or_tag=None, org=None, course=None, category=None, name=None, revision=None):
|
||||
"""
|
||||
Create a new location that is a clone of the specifed one.
|
||||
|
||||
@@ -70,11 +70,15 @@ class Location(_LocationBase):
|
||||
wildcard selection
|
||||
"""
|
||||
|
||||
|
||||
if org is None and course is None and category is None and name is None and revision is None:
|
||||
location = loc_or_tag
|
||||
else:
|
||||
location = (loc_or_tag, org, course, category, name, revision)
|
||||
|
||||
if location is None:
|
||||
return _LocationBase.__new__(_cls, *([None] * 6))
|
||||
|
||||
def check_dict(dict_):
|
||||
check_list(dict_.itervalues())
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from xmodule.mako_module import MakoDescriptorSystem
|
||||
from mitxmako.shortcuts import render_to_string
|
||||
from bson.son import SON
|
||||
from itertools import repeat
|
||||
from fs.osfs import OSFS
|
||||
|
||||
from . import ModuleStore, Location
|
||||
from .exceptions import ItemNotFoundError, InsufficientSpecificationError
|
||||
@@ -61,7 +62,9 @@ class MongoModuleStore(ModuleStore):
|
||||
"""
|
||||
A Mongodb backed ModuleStore
|
||||
"""
|
||||
def __init__(self, host, db, collection, port=27017, default_class=None):
|
||||
|
||||
# TODO (cpennington): Enable non-filesystem filestores
|
||||
def __init__(self, host, db, collection, fs_root, port=27017, default_class=None):
|
||||
self.collection = pymongo.connection.Connection(
|
||||
host=host,
|
||||
port=port
|
||||
@@ -77,6 +80,7 @@ class MongoModuleStore(ModuleStore):
|
||||
module_path, _, class_name = default_class.rpartition('.')
|
||||
class_ = getattr(import_module(module_path), class_name)
|
||||
self.default_class = class_
|
||||
self.fs_root = fs_root
|
||||
|
||||
def _clean_item_data(self, item):
|
||||
"""
|
||||
@@ -113,13 +117,27 @@ class MongoModuleStore(ModuleStore):
|
||||
|
||||
return data
|
||||
|
||||
def _load_item(self, item, data_cache):
|
||||
"""
|
||||
Load an XModuleDescriptor from item, using the children stored in data_cache
|
||||
"""
|
||||
resource_fs = OSFS(self.fs_root / item.get('data_dir', item['location']['course']))
|
||||
system = CachingDescriptorSystem(
|
||||
self,
|
||||
data_cache,
|
||||
self.default_class,
|
||||
resource_fs,
|
||||
render_to_string
|
||||
)
|
||||
return system.load_item(item['location'])
|
||||
|
||||
def _load_items(self, items, depth=0):
|
||||
"""
|
||||
Load a list of xmodules from the data in items, with children cached up to specified depth
|
||||
"""
|
||||
data_cache = self._cache_children(items, depth)
|
||||
system = CachingDescriptorSystem(self, data_cache, self.default_class, None, render_to_string)
|
||||
return [system.load_item(item['location']) for item in items]
|
||||
|
||||
return [self._load_item(item, data_cache) for item in items]
|
||||
|
||||
def get_item(self, location, depth=0):
|
||||
"""
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<course name="Toy Course" graceperiod="1 day 5 hours 59 minutes 59 seconds" showanswer="always" rerandomize="never">
|
||||
<chapter name="Overview">
|
||||
<section format="Video" name="Welcome">
|
||||
<video youtube="0.75:izygArpw-Qo,1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8"/>
|
||||
</section>
|
||||
<section format="Lecture Sequence" name="System Usage Sequence">
|
||||
<html id="Lab2A" filename="Lab2A.html"/>
|
||||
<video name="Welcome" youtube="0.75:izygArpw-Qo,1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8"/>
|
||||
<videosequence format="Lecture Sequence" name="System Usage Sequence">
|
||||
<html id="Lab2A" filename="Lab2A"/>
|
||||
<video name="S0V1: Video Resources" youtube="0.75:EuzkdzfR0i8,1.0:1bK-WdDi6Qw,1.25:0v1VzoDVUTM,1.50:Bxk_-ZJb240"/>
|
||||
</section>
|
||||
</videosequence>
|
||||
</chapter>
|
||||
</course>
|
||||
|
||||
105
common/test/data/toy/html/Lab2A.html
Normal file
105
common/test/data/toy/html/Lab2A.html
Normal file
@@ -0,0 +1,105 @@
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$("#r1_slider").slider({
|
||||
value: 1, min: 1, max: 10, step: 1, slide: schematic.component_slider,
|
||||
schematic: "ctrls", component: "R1", property: "r", analysis: "dc",
|
||||
})
|
||||
|
||||
$("#r2_slider").slider({
|
||||
value: 1, min: 1, max: 10, step: 1, slide: schematic.component_slider,
|
||||
schematic: "ctrls", component: "R2", property: "r", analysis: "dc",
|
||||
})
|
||||
|
||||
$("#r3_slider").slider({
|
||||
value: 1, min: 1, max: 10, step: 1, slide: schematic.component_slider,
|
||||
schematic: "ctrls", component: "R3", property: "r", analysis: "dc",
|
||||
})
|
||||
|
||||
$("#r4_slider").slider({
|
||||
value: 1, min: 1, max: 10, step: 1, slide: schematic.component_slider,
|
||||
schematic: "ctrls", component: "R4", property: "r", analysis: "dc",
|
||||
})
|
||||
|
||||
$("#slider").slider(); });
|
||||
</script>
|
||||
|
||||
<b>Lab 2A: Superposition Experiment</b>
|
||||
|
||||
<br><br><i>Note: This part of the lab is just to develop your intuition about
|
||||
superposition. There are no responses that need to be checked.</i>
|
||||
|
||||
<br/><br/>Circuits with multiple sources can be hard to analyze as-is. For example, what is the voltage
|
||||
between the two terminals on the right of Figure 1?
|
||||
|
||||
<center>
|
||||
<input width="425" type="hidden" height="150" id="schematic1" parts="" analyses="" class="schematic ctrls" name="test2" value="[["w",[160,64,184,64]],["w",[160,16,184,16]],["w",[64,16,112,16]],["w",[112,64,88,64]],["w",[64,64,88,64]],["g",[88,64,0],{},["0"]],["w",[112,64,160,64]],["w",[16,64,64,64]],["r",[160,16,0],{"name":"R4","r":"1"},["1","0"]],["r",[160,16,1],{"name":"R3","r":"1"},["1","2"]],["i",[112,64,6],{"name":"","value":"6A"},["0","2"]],["r",[64,16,0],{"name":"R2","r":"1"},["2","0"]],["r",[64,16,1],{"name":"R1","r":"1"},["2","3"]],["v",[16,16,0],{"name":"","value":"8V"},["3","0"]],["view",-24,0,2]]"/>
|
||||
Figure 1. Example multi-source circuit
|
||||
</center>
|
||||
|
||||
<br/><br/>We can use superposition to make the analysis much easier.
|
||||
The circuit in Figure 1 can be decomposed into two separate
|
||||
subcircuits: one involving only the voltage source and one involving only the
|
||||
current source. We'll analyze each circuit separately and combine the
|
||||
results using superposition. Recall that to decompose a circuit for
|
||||
analysis, we'll pick each source in turn and set all the other sources
|
||||
to zero (i.e., voltage sources become short circuits and current
|
||||
sources become open circuits). The circuit above has two sources, so
|
||||
the decomposition produces two subcircuits, as shown in Figure 2.
|
||||
|
||||
<center>
|
||||
<table><tr><td>
|
||||
<input style="display:inline;" width="425" type="hidden" height="150" id="schematic2" parts="" analyses="" class="schematic ctrls" name="test2" value="[["w",[160,64,184,64]],["w",[160,16,184,16]],["w",[64,16,112,16]],["w",[112,64,88,64]],["w",[64,64,88,64]],["g",[88,64,0],{},["0"]],["w",[112,64,160,64]],["w",[16,64,64,64]],["r",[160,16,0],{"name":"R4","r":"1"},["1","0"]],["r",[160,16,1],{"name":"R3","r":"1"},["1","2"]],["r",[64,16,0],{"name":"R2","r":"1"},["2","0"]],["r",[64,16,1],{"name":"R1","r":"1"},["2","3"]],["v",[16,16,0],{"name":"","value":"8V"},["3","0"]],["view",-24,0,2]]"/>
|
||||
(a) Subcircuit for analyzing contribution of voltage source
|
||||
</td><td>
|
||||
<input width="425" type="hidden" height="150" id="schematic3" parts="" analyses="" class="schematic ctrls" name="test2" value="[["w",[16,16,16,64]],["w",[160,64,184,64]],["w",[160,16,184,16]],["w",[64,16,112,16]],["w",[112,64,88,64]],["w",[64,64,88,64]],["g",[88,64,0],{},["0"]],["w",[112,64,160,64]],["w",[16,64,64,64]],["r",[160,16,0],{"name":"R4","r":"1"},["1","0"]],["r",[160,16,1],{"name":"R3","r":"1"},["1","2"]],["i",[112,64,6],{"name":"","value":"6A"},["0","2"]],["r",[64,16,0],{"name":"R2","r":"1"},["2","0"]],["r",[64,16,1],{"name":"R1","r":"1"},["2","3"]],["view",-24,0,2]]"/>
|
||||
(b) Subcircuit for analyzing contribution of current source
|
||||
</td></tr></table>
|
||||
<br>Figure 2. Decomposition of Figure 1 into subcircuits
|
||||
</center>
|
||||
|
||||
<br/>Let's use the DC analysis capability of the schematic tool to see superposition
|
||||
in action. The sliders below control the resistances of R1, R2, R3 and R4 in all
|
||||
the diagrams. As you move the sliders, the schematic tool will adjust the appropriate
|
||||
resistance, perform a DC analysis and display the node voltages on the diagrams. Here's
|
||||
what you want to observe as you play with the sliders:
|
||||
|
||||
<ul style="margin-left:2em;margin-top:1em;margin-right:2em;margin-bottom:1em;">
|
||||
<i>The voltage for a node in Figure 1 is the sum of the voltages for
|
||||
that node in Figures 2(a) and 2(b), just as predicted by
|
||||
superposition. (Note that due to round-off in the display of the
|
||||
voltages, the sum of the displayed voltages in Figure 2 may only be within
|
||||
.01 of the voltages displayed in Figure 1.)</i>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<center>
|
||||
<table><tr valign="top">
|
||||
<td>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td>R1</td>
|
||||
<td>
|
||||
<div id="r1_slider" style="width:200px; height:10px; margin-left:15px"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td>R2</td>
|
||||
<td>
|
||||
<div id="r2_slider" style="width:200px; height:10px; margin-left:15px; margin-top:10px;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td>R3</td>
|
||||
<td>
|
||||
<div id="r3_slider" style="width:200px; height:10px; margin-left:15px; margin-top:10px;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td>R4</td>
|
||||
<td>
|
||||
<div id="r4_slider" style="width:200px; height:10px; margin-left:15px; margin-top:10px;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td></tr></table>
|
||||
</center>
|
||||
@@ -1,28 +0,0 @@
|
||||
GRADER = [
|
||||
{
|
||||
'type' : "Homework",
|
||||
'min_count' : 12,
|
||||
'drop_count' : 2,
|
||||
'short_label' : "HW",
|
||||
'weight' : 0.15,
|
||||
},
|
||||
{
|
||||
'type' : "Lab",
|
||||
'min_count' : 12,
|
||||
'drop_count' : 2,
|
||||
'category' : "Labs",
|
||||
'weight' : 0.15
|
||||
},
|
||||
{
|
||||
'type' : "Midterm",
|
||||
'name' : "Midterm Exam",
|
||||
'short_label' : "Midterm",
|
||||
'weight' : 0.3,
|
||||
},
|
||||
{
|
||||
'type' : "Final",
|
||||
'name' : "Final Exam",
|
||||
'short_label' : "Final",
|
||||
'weight' : 0.4,
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user